﻿using System;
using System.IO;
using System.Drawing;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;
using System.Configuration;
using System.Globalization;
// using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace FolderFileMenu
{
    public partial class MainForm : Form
    {
        static string thisPath = Assembly.GetExecutingAssembly().Location; // = Application.StartupPath;
        static string thisFolder = Path.GetDirectoryName(thisPath);
        static string folderName = Path.GetFileName(thisFolder); // = new DirectoryInfo(thisFolder).Name;
        static string exeName = Path.GetFileNameWithoutExtension(thisPath); // (Assembly.GetExecutingAssembly().CodeBase);
        List<FileInfo> fileList = new List<FileInfo>();
        static string ciName = CultureInfo.CurrentUICulture.Name; // de-DE etc.
        static int offSetX = 40, offSetY = 40;
        ShellContextMenu contextMenu = new ShellContextMenu();
        static bool noClose = false;
        static bool showSettings = false;
        private int lastListBoxItemIndex;
        private Point lastMouseLocation = new Point(-1, -1);
        static bool noCursorPostion = false;
        public static bool NoCursorPostion { get { return noCursorPostion; } }
        private static Point _point = new System.Drawing.Point(Cursor.Position.X, Cursor.Position.Y);
        public static int OffSetX { get { return offSetX; } }
        public static int OffSetY { get { return offSetY; } }
        public static Point _Point { get { return _point; } }
        public HelpForm helpForm = new HelpForm();


        public MainForm()
        {
            string[] args = Environment.GetCommandLineArgs();

            InitializeComponent();
            if (args.Length > 1 && (WildcardMatch.IsLike("?center", args[1]) || WildcardMatch.IsLike("?c", args[1]))) { noCursorPostion = true; }
            else { Location = new Point(_point.X - offSetX, _point.Y - offSetY); } // _point = new Point(); // => isEmpty (look at WaitWindow.cs)

            if (!File.Exists(thisPath + ".config"))
            {
                MessageBox.Show("'" + thisPath + ".Config' not found." + Environment.NewLine + "Please reinstall: '" + exeName + ".exe' + '" + exeName + ".exe.Config'.", exeName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                Environment.Exit(0); //Application.Exit() funktioniert hier nicht
            }

            string firstRun = ReadAppSetting("FirstRun"); // ConfigurationManager.AppSettings["FirstRun"];
            if (string.IsNullOrEmpty(firstRun)) { firstRun = "1"; } // verhindert Programmabsturz
            if (firstRun.Equals("1"))
            {
                MessageBox.Show("Attention, please read this information, which is displayed only once!" + Environment.NewLine + Environment.NewLine + Environment.NewLine
                    + "How to change settings:" + Environment.NewLine + "¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯" + Environment.NewLine + "• Hold down the shift key during startup to open the settings dialog."
                    + Environment.NewLine + Environment.NewLine + "• Or right-click the window title and select 'Settings' from the menu." + Environment.NewLine + Environment.NewLine
                    + "Because this is the first run, the settings dialog will be displayed next.", exeName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                try
                {
                    Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); // if file.exists: Program.cs
                    if (config.AppSettings.Settings["FirstRun"] == null)
                    {
                        config.AppSettings.Settings.Add("FirstRun", "0");
                    }
                    config.AppSettings.Settings["FirstRun"].Value = "0";
                    config.Save(ConfigurationSaveMode.Modified);
                    ConfigurationManager.RefreshSection("appSettings"); // Aktualisiert den benannten Abschnitt. Dadurch wird er beim nächsten Abrufen erneut vom Datenträger gelesen.
                }// you don't need to Close/Dispose. The Configuration class returned by the OpenExeConfiguration static method doesn't implement IDisposable nor it has any Close method 
                catch (ConfigurationErrorsException) { MessageBox.Show("Error writing app settings", thisFolder, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); }
                showSettings = true;
            }

            using (WaitWindow f2 = new WaitWindow())
            {
                f2.Show(); // Please wait...
                f2.Update();

                string fileScheme = ReadAppSetting("FileScheme").Replace(" ", String.Empty);
                if (string.IsNullOrEmpty(fileScheme)) { fileScheme = "*.*"; } // verhindert Programmabsturz
                List<string> searchPatternList = new List<string>(fileScheme.Split(','));

                string excludeFiles = ReadAppSetting("ExcludeFiles"); // Pfad darf Leerzeichen enthalten
                if (string.IsNullOrEmpty(excludeFiles)) { excludeFiles = string.Empty; } // verhindert Programmabsturz
                List<string> excludeFilesList = new List<string>(excludeFiles.Split(','));

                string excludeFolders = ReadAppSetting("ExcludeFolders"); // Pfad darf Leerzeichen enthalten
                if (string.IsNullOrEmpty(excludeFolders)) { excludeFolders = string.Empty; } // verhindert Programmabsturz
                List<string> excludeFoldersList = new List<string>(excludeFolders.Split(','));

                string recurseOption = ReadAppSetting("SubFolders");
                if (string.IsNullOrEmpty(recurseOption)) { recurseOption = "0"; } // verhindert Programmabsturz
                bool includeSubfolders = recurseOption.Equals("1") ? true : false;
                foreach (string ext in searchPatternList)
                {
                    foreach (var subFile in Directory.GetFiles(thisFolder, ext, includeSubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
                    {
                        FileInfo file = new FileInfo(subFile);
                        bool isExcludeFilename = false;
                        foreach (string excFile in excludeFilesList)
                        {// if (!string.IsNullOrEmpty(excFile.Trim()) && file.Name.Contains(excFile.Trim())) { isExcludeFilename = true; }
                            if (!string.IsNullOrEmpty(excFile.Trim()) && WildcardMatch.IsLike(excFile.Trim(), file.Name)) { isExcludeFilename = true; }
                        }

                        bool isInExcludeFolder = false;
                        if (includeSubfolders)
                        {
                            foreach (string excFolder in excludeFoldersList)
                            {// if (!string.IsNullOrEmpty(excFolder.Trim()) && file.FullName.Contains(excFolder.Trim() + Path.DirectorySeparatorChar)) { isInExcludeFolder = true; }
                                string str = file.Directory.ToString().Substring(thisFolder.Length); // FileInfo.Directory: Gets an instance of the parent directory.
                                string[] directories = str.Split(Path.DirectorySeparatorChar);
                                foreach (string dir in directories)
                                {
                                    if (!string.IsNullOrEmpty(dir) && !string.IsNullOrEmpty(excFolder.Trim()) && WildcardMatch.IsLike(excFolder.Trim(), dir)) { isInExcludeFolder = true; }
                                }
                            }
                        }
                        if (!isExcludeFilename && !isInExcludeFolder && !file.Name.StartsWith(exeName)) { fileList.Add(file); } // zum Programm gehörige Dateien werden ausgeschlossen
                    }
                }
                string sortLastModified = ReadAppSetting("SortLastModified");
                if (string.IsNullOrEmpty(sortLastModified)) { sortLastModified = "0"; } // verhindert Programmabsturz
                if (sortLastModified.Equals("1")) { fileList.Sort(new DateCompareFileInfo()); }
                else { fileList.Sort(new PathCompareFileInfo()); } // Sort by folder

                if (fileList.Count != 0)
                {
                    Text = thisFolder;
                    int imgListBoxWidth = imageListBox.ClientRectangle.Width;
                    int formHeight = ClientRectangle.Width;
                    int rowWidth = 0;
                    int leftMargin = Convert.ToInt16(imageListBox.Margin.Left);
                    int rightMargin = Convert.ToInt16(imageListBox.Margin.Right);
                    int topMargin = Convert.ToInt16(imageListBox.Margin.Top);
                    int bottomMargin = Convert.ToInt16(imageListBox.Margin.Bottom);
                    int availableWidth = Screen.PrimaryScreen.WorkingArea.Width;
                    int availableHeight = Screen.PrimaryScreen.WorkingArea.Height;
                    Rectangle screenRectangle = RectangleToScreen(this.ClientRectangle);
                    int titleHeight = screenRectangle.Top - this.Top;
                    //int leftBorderWidth = screenRectangle.Left - base.Left;

                    Graphics g = imageListBox.CreateGraphics();
                    IntPtr hImgSmall; //the handle to the system image list 
                    NativeWin32.SHFILEINFO shinfo = new NativeWin32.SHFILEINFO();
                    Dictionary<string, ImageListBoxItem> listItem = new Dictionary<string, ImageListBoxItem>();
                    foreach (FileInfo fileName in fileList) //  for (int i = 0; i < fileList.Count; i++)  FileInfo fileName = fileList[i];
                    {
                        string shortName = fileName.Name;
                        listItem[shortName] = new ImageListBoxItem();

                        if (!imageList.Images.ContainsKey(fileName.Extension))
                        {// Check to see if the image collection contains an image for this extension, using the extension as a key.
                            hImgSmall = NativeWin32.SHGetFileInfo(fileName.FullName, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), NativeWin32.SHGFI_ICON | NativeWin32.SHGFI_SMALLICON);
                            Icon iconForFile = Icon.FromHandle(shinfo.hIcon); //The icon is returned in the hIcon member of the shinfo struct
                            //Icon iconForFile = Icon.ExtractAssociatedIcon(fileName.FullName); // liefert nur 32x32!
                            imageList.Images.Add(fileName.Extension, iconForFile); // If not, add the image to the image list.
                        }

                        listItem[shortName].Text = shortName; listItem[shortName].ImageIndex = imageList.Images.IndexOfKey(fileName.Extension);
                        imageListBox.Items.Add(listItem[shortName]); // rowWidth = (int)g.MeasureString(shortName, imageListBox.Font, 0, StringFormat.GenericTypographic).Width + 25;

                        rowWidth = (int)g.MeasureString(imageListBox.Items[imageListBox.Items.Count - 1].ToString(), imageListBox.Font).Width; // Determine the size for HorizontalExtent using the MeasureString method using the last item in the list

                        if (rowWidth > imgListBoxWidth && rowWidth < availableWidth - leftMargin - rightMargin - 8)
                        {
                            imgListBoxWidth = rowWidth > availableWidth / 4 ? availableWidth / 4 : rowWidth;
                        } // falls erforderlich Breite erhöhen
                    }
                    g.Dispose();

                    Width = imgListBoxWidth + leftMargin + rightMargin + SystemInformation.VerticalScrollBarWidth + 20; // 16 für Image + 4
                    formHeight = fileList.Count * imageListBox.ItemHeight + topMargin + bottomMargin + titleHeight + 4;
                    Height = formHeight > availableHeight ? availableHeight : formHeight;
                    //MessageBox.Show("availableHeight: " + availableHeight + "\ntopMargin: " + topMargin + "\nbottomMargin: " + bottomMargin + "\ntitleHeight: " + titleHeight + "\nleftBorderWidth" + leftBorderWidth);

                    int _top = _point.Y, _left = _point.X;
                    if (availableHeight - _point.Y < imageListBox.Height + titleHeight) // 30 für Form-Titelzeile
                    {
                        _top = _point.Y - (imageListBox.Height - (availableHeight - _point.Y) + titleHeight + 2);
                        Top = _top < 0 ? 0 : _top;
                    }
                    else { imageListBox.SelectedIndex = 0; }

                    if (availableWidth - _point.X < imageListBox.Width + 8) // 12 für Form-Rand
                    {
                        _left = _point.X - (imageListBox.Width - (availableWidth - _point.X) + 8);
                        Left = _left < 0 ? 0 : _left;
                    }
                    if (noCursorPostion) { CenterToScreen(); }
                    string desktopShortcut = ReadAppSetting("DesktopShortcut");
                    if (string.IsNullOrEmpty(desktopShortcut)) { desktopShortcut = "1"; } // verhindert Programmabsturz
                    if (desktopShortcut.Equals("1"))
                    {// ACHTUNG: erfolgt nur, wenn fileList Dateien enthält!!!
                        desktopShortcut = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), folderName + "-FileList.lnk");
                        if (!File.Exists(desktopShortcut)) // && imageListBox.Items.Count > 0)
                        { AddShortcutToDesktop(desktopShortcut); }
                    }
                }
                else
                {
                    imageListBox.Height = imageListBox.ItemHeight;
                    Text = folderName;
                }
                //Thread.Sleep(1000);
                f2.Close(); // wahrscheinlich nicht nötig
            } // WaitWindow
        }

        public static string ReadAppSetting(string key)
        {
            string ret = string.Empty;
            try
            {
                if (ConfigurationManager.AppSettings[key] != null) { ret = ConfigurationManager.AppSettings[key]; }
            }
            catch (ConfigurationErrorsException) { MessageBox.Show("Error reading app settings", thisFolder, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); }
            return ret;
        }

        private void MainForm_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Escape) Close(); }

        private void DoAction(int index)
        {
            string menuFile = fileList[index].FullName;
            try
            {
                noClose = true;
                Hide();
                File.SetLastAccessTime(menuFile, DateTime.Now); // The NtfsDisableLastAccessUpdate registry setting is enabled by default in Windows 7 - 10.
                                                                // This was a performance tweak that many people used in earlier OSes because it prevents
                                                                // a lot of excessive writes to the hard drive.
                                                                // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem - NtfsDisableLastAccessUpdate
                Process.Start(menuFile);
            }
            catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); }
            finally { Close(); }
        }

        private void AddShortcutToDesktop(string linkFileName)
        {// ACHTUNG: erfolgt nur, wenn fileList Dateien enthält!!!
            string iconPath = ReadAppSetting("IconPath");
            string iconIndex = ReadAppSetting("IconIndex");
            int.TryParse(iconIndex, out int x);
            if (string.IsNullOrEmpty(iconPath)) { iconPath = Path.Combine(thisFolder, exeName + ".ico"); } // verhindert Programmabsturz
            using (var shellShortcut = new ShellShortcut(linkFileName)
            {
                Path = thisPath, // Application.ExecutablePath,
                WorkingDirectory = thisFolder, // Application.StartupPath,
                Arguments = "",
                IconPath = File.Exists(iconPath) ? iconPath : thisPath,
                IconIndex = x,
                Description = "Hold down the shift key during startup to open the settings dialog.",
            })
                try
                {
                    shellShortcut.Save();
                    MessageBox.Show("The desktop shortcut has been created successfully:" + Environment.NewLine + linkFileName, thisFolder, MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message, thisFolder, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            string openFolder = ciName.StartsWith("de") ? "&Ordner öffnen\tAlt+F3" : "&Open folder\tAlt+F3";
            string settings = ciName.StartsWith("de") ? "&Einstellungen\tAlt+F2" : "Open &settings\tAlt+F2";
            IntPtr sysMenuHandle = NativeWin32.GetSystemMenu(Handle, false);
            NativeWin32.AppendMenu(sysMenuHandle, NativeWin32.MF_BYPOSITION | NativeWin32.MF_SEPARATOR, 0, string.Empty);
            NativeWin32.AppendMenu(sysMenuHandle, NativeWin32.MF_BYPOSITION, NativeWin32.IDM_CUSTOMITEM1, openFolder);
            NativeWin32.AppendMenu(sysMenuHandle, NativeWin32.MF_BYPOSITION, NativeWin32.IDM_CUSTOMITEM2, settings);
            if (showSettings || (ModifierKeys & Keys.Shift) != 0) // This will also be true if Ctrl+Shift is down
            {// if (Control.ModifierKeys == Keys.Shift) // If you're in a class that inherits Control (such as a form), you can remove the Control.
                try
                {//noClose = true; Hide(); => nicht erforderlich weil "Show" gar nicht erreicht wird
                    using (OptionsForm frmOptions = new OptionsForm())
                    {
                        frmOptions.ShowDialog(this);
                    }
                }
                catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); }
                finally { Close(); }
            }
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            contextMenu.ReleaseHandle();
            contextMenu.DestroyHandle();
        }

        protected override void WndProc(ref Message m)
        {
            if ((m.Msg == NativeWin32.WM_SYSCOMMAND) && ((m.WParam.ToInt32() == NativeWin32.IDM_CUSTOMITEM1) || (m.WParam.ToInt32() == NativeWin32.IDM_CUSTOMITEM2)))
            {
                try
                {
                    noClose = true;
                    Hide();
                    if (m.WParam.ToInt32() == NativeWin32.IDM_CUSTOMITEM2)
                    {
                        using (OptionsForm frmOptions = new OptionsForm()) { frmOptions.ShowDialog(); }
                    }
                    else if ((m.WParam.ToInt32() == NativeWin32.IDM_CUSTOMITEM1)) { Process.Start(thisFolder + Path.DirectorySeparatorChar); }
                }
                catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); }
                finally { Close(); }
            }
            else { base.WndProc(ref m); }
        }

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            switch (keyData)
            {
                case Keys.F2 | Keys.Alt:
                    try
                    {
                        noClose = true;
                        Hide();
                        using (OptionsForm frmOptions = new OptionsForm()) { frmOptions.ShowDialog(); }
                    }
                    catch (Exception ex) { MessageBox.Show(ex.Message, "Error"); }
                    finally { Close(); }
                    return true;
                case Keys.F3 | Keys.Alt:
                    Process.Start(thisFolder + Path.DirectorySeparatorChar); 
                    return true;
                case Keys.Escape:
                    Close();
                    return true;
            }
            return base.ProcessCmdKey(ref msg, keyData);
        }

        private void MainForm_Deactivate(object sender, EventArgs e)
        {// Tritt ein, wenn das Formular den Fokus verliert und nicht mehr das aktive Formular ist.
            if (!noClose) { Close(); }
            noClose = false; // aktuell nicht erforderlich: "noClose = true" wird nur 2x in try-Block eingesetzt, jeweils gefolgt von "finally: Close();"
        }

        private void ImageListBox_KeyDown(object sender, KeyEventArgs e)
        {// if (e.KeyCode == Keys.Escape) { Close(); }
            if (e.KeyCode == Keys.Enter) { DoAction(imageListBox.SelectedIndex); }
            else if (e.KeyCode == Keys.Apps)
            {
                if (imageListBox.SelectedItems.Count > 0)
                {// ShellContextMenu contextMenuMnu = new ShellContextMenu();
                    FileInfo[] arrayFileInfo = new FileInfo[1];
                    arrayFileInfo[0] = new FileInfo(fileList[imageListBox.SelectedIndex].FullName);
                    contextMenu.ShowContextMenu(arrayFileInfo, imageListBox.PointToScreen(new Point(offSetX, imageListBox.ItemHeight * imageListBox.SelectedIndex + 10)));
                }
            }
        }

        private void ImageListBox_MouseClick(object sender, MouseEventArgs e)
        {
            if (imageListBox.SelectedItems.Count > 0) { DoAction(imageListBox.SelectedIndex); }
        }

        private void ImageListBox_MouseMove(object sender, MouseEventArgs e)
        {
            int itemIndex = imageListBox.IndexFromPoint(e.Location);
            imageListBox.SelectedIndex = itemIndex;
            if (itemIndex != lastListBoxItemIndex && itemIndex > -1 && itemIndex <= imageListBox.Items.Count)
            {
                lastListBoxItemIndex = itemIndex;
                toolTip.RemoveAll(); // toolTip.SetToolTip(this, null); // ToolTip zurücksetzen
                string tipText = fileList[itemIndex].FullName;
                toolTip.SetToolTip(imageListBox, tipText);
            }
        }

        private void ImageListBox_MouseUp(object sender, MouseEventArgs e)
        {
            if (imageListBox.SelectedItems.Count > 0)
            {
                if (e.Button == MouseButtons.Right)
                {//ShellContextMenu contextMenu = new ShellContextMenu();
                    FileInfo[] arrayFileInfo = new FileInfo[1];
                    arrayFileInfo[0] = new FileInfo(fileList[imageListBox.SelectedIndex].FullName);
                    contextMenu.ShowContextMenu(arrayFileInfo, PointToScreen(new Point(e.X, e.Y)));
                }
            }
        }

        private void MainForm_HelpRequested(object sender, HelpEventArgs hlpevent)
        {
            hlpevent.Handled = true;
            if (helpForm != null)
            {
                noClose = true;
                helpForm.ShowDialog(); // F1 würde mehrfach Form öffnen}
            }
        }

        private void MainForm_HelpButtonClicked(object sender, CancelEventArgs e)
        {
            e.Cancel = true;
            noClose = true;
            helpForm.ShowDialog();
        }

        private void MainForm_Shown(object sender, EventArgs e)
        {
            TopMost = true;
            BringToFront();
            Activate();
        }

        //private DateTime _lastKeyPress;
        //private string _searchString;
        //private void imageListBox_KeyPress(object sender, KeyPressEventArgs e)
        //{
        //    //var newDate = DateTime.Now;
        //    //var diff = newDate - _lastKeyPress;
        //    //if (diff.TotalSeconds >= 2)
        //    //    _searchString = string.Empty;
        //    //_searchString += e.KeyChar;
        //    //for (var i = imageListBox.SelectedIndex; i < imageListBox.Items.Count; i++)
        //    //{
        //    //    var item = imageListBox.Items[i].ToString();
        //    //    if (item.ToLower().StartsWith(_searchString))
        //    //    {
        //    //        imageListBox.SelectedIndex = i;
        //    //        break;
        //    //    }
        //    //}
        //    //_lastKeyPress = newDate;
        //    //e.Handled = true; //REALLY IMPORTANT TO HAVE THIS      
        //}

    }
}
